// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ui/wm/core/window_animations.h"

#include <math.h>

#include <algorithm>
#include <vector>

#include "base/command_line.h"
#include "base/compiler_specific.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/message_loop/message_loop.h"
#include "base/stl_util.h"
#include "base/time/time.h"
#include "ui/aura/client/aura_constants.h"
#include "ui/aura/window.h"
#include "ui/aura/window_delegate.h"
#include "ui/aura/window_observer.h"
#include "ui/aura/window_property.h"
#include "ui/compositor/compositor_observer.h"
#include "ui/compositor/layer.h"
#include "ui/compositor/layer_animation_observer.h"
#include "ui/compositor/layer_animation_sequence.h"
#include "ui/compositor/layer_animator.h"
#include "ui/compositor/layer_tree_owner.h"
#include "ui/compositor/scoped_animation_duration_scale_mode.h"
#include "ui/compositor/scoped_layer_animation_settings.h"
#include "ui/gfx/animation/animation.h"
#include "ui/gfx/geometry/rect_conversions.h"
#include "ui/gfx/geometry/vector2d.h"
#include "ui/gfx/geometry/vector3d_f.h"
#include "ui/gfx/interpolated_transform.h"
#include "ui/gfx/screen.h"
#include "ui/wm/core/window_util.h"
#include "ui/wm/core/wm_core_switches.h"
#include "ui/wm/public/animation_host.h"

DECLARE_WINDOW_PROPERTY_TYPE(wm::WindowVisibilityAnimationType)
DECLARE_WINDOW_PROPERTY_TYPE(wm::WindowVisibilityAnimationTransition)
DECLARE_WINDOW_PROPERTY_TYPE(float)

namespace wm {
namespace {
    const float kWindowAnimation_Vertical_TranslateY = 15.f;

    // A base class for hiding animation observer which has two roles:
    // 1) Notifies AnimationHost at the end of hiding animation.
    // 2) Detaches the window's layers for hiding animation and deletes
    // them upon completion of the animation. This is necessary to a)
    // ensure that the animation continues in the event of the window being
    // deleted, and b) to ensure that the animation is visible even if the
    // window gets restacked below other windows when focus or activation
    // changes.
    // The subclass will determine when the animation is completed.
    class HidingWindowAnimationObserverBase : public aura::WindowObserver {
    public:
        explicit HidingWindowAnimationObserverBase(aura::Window* window)
            : window_(window)
        {
            window_->AddObserver(this);
        }
        ~HidingWindowAnimationObserverBase() override
        {
            if (window_)
                window_->RemoveObserver(this);
        }

        // aura::WindowObserver:
        void OnWindowDestroying(aura::Window* window) override
        {
            DCHECK_EQ(window, window_);
            WindowInvalid();
        }

        void OnWindowDestroyed(aura::Window* window) override
        {
            DCHECK_EQ(window, window_);
            WindowInvalid();
        }

        // Detach the current layers and create new layers for |window_|.
        // Stack the original layers above |window_| and its transient
        // children.  If the window has transient children, the original
        // layers will be moved above the top most transient child so that
        // activation change does not put the window above the animating
        // layer.
        void DetachAndRecreateLayers()
        {
            layer_owner_ = RecreateLayers(window_);
            if (window_->parent()) {
                const aura::Window::Windows& transient_children = GetTransientChildren(window_);
                aura::Window::Windows::const_iterator iter = std::find(window_->parent()->children().begin(),
                    window_->parent()->children().end(),
                    window_);
                DCHECK(iter != window_->parent()->children().end());
                aura::Window* topmost_transient_child = NULL;
                for (++iter; iter != window_->parent()->children().end(); ++iter) {
                    if (ContainsValue(transient_children, *iter))
                        topmost_transient_child = *iter;
                }
                if (topmost_transient_child) {
                    window_->parent()->layer()->StackAbove(
                        layer_owner_->root(), topmost_transient_child->layer());
                }
            }
        }

    protected:
        // Invoked when the hiding animation is completed.  It will delete
        // 'this', and no operation should be made on this object after this
        // point.
        void OnAnimationCompleted()
        {
            // Window may have been destroyed by this point.
            if (window_) {
                aura::client::AnimationHost* animation_host = aura::client::GetAnimationHost(window_);
                if (animation_host)
                    animation_host->OnWindowHidingAnimationCompleted();
                window_->RemoveObserver(this);
            }
            delete this;
        }

    private:
        // Invoked when the window is destroyed (or destroying).
        void WindowInvalid()
        {
            layer_owner_->root()->SuppressPaint();

            window_->RemoveObserver(this);
            window_ = NULL;
        }

        aura::Window* window_;

        // The owner of detached layers.
        scoped_ptr<ui::LayerTreeOwner> layer_owner_;

        DISALLOW_COPY_AND_ASSIGN(HidingWindowAnimationObserverBase);
    };

} // namespace

DEFINE_WINDOW_PROPERTY_KEY(int,
    kWindowVisibilityAnimationTypeKey,
    WINDOW_VISIBILITY_ANIMATION_TYPE_DEFAULT);
DEFINE_WINDOW_PROPERTY_KEY(int, kWindowVisibilityAnimationDurationKey, 0);
DEFINE_WINDOW_PROPERTY_KEY(WindowVisibilityAnimationTransition,
    kWindowVisibilityAnimationTransitionKey,
    ANIMATE_BOTH);
DEFINE_WINDOW_PROPERTY_KEY(float,
    kWindowVisibilityAnimationVerticalPositionKey,
    kWindowAnimation_Vertical_TranslateY);

// A HidingWindowAnimationObserver that deletes observer and detached
// layers upon the completion of the implicit animation.
class ImplicitHidingWindowAnimationObserver
    : public HidingWindowAnimationObserverBase,
      public ui::ImplicitAnimationObserver {
public:
    ImplicitHidingWindowAnimationObserver(
        aura::Window* window,
        ui::ScopedLayerAnimationSettings* settings);
    ~ImplicitHidingWindowAnimationObserver() override { }

    // ui::ImplicitAnimationObserver:
    void OnImplicitAnimationsCompleted() override;

private:
    DISALLOW_COPY_AND_ASSIGN(ImplicitHidingWindowAnimationObserver);
};

namespace {

    const int kDefaultAnimationDurationForMenuMS = 150;

    const float kWindowAnimation_HideOpacity = 0.f;
    const float kWindowAnimation_ShowOpacity = 1.f;
    const float kWindowAnimation_TranslateFactor = 0.5f;
    const float kWindowAnimation_ScaleFactor = .95f;

    const int kWindowAnimation_Rotate_DurationMS = 180;
    const int kWindowAnimation_Rotate_OpacityDurationPercent = 90;
    const float kWindowAnimation_Rotate_TranslateY = -20.f;
    const float kWindowAnimation_Rotate_PerspectiveDepth = 500.f;
    const float kWindowAnimation_Rotate_DegreesX = 5.f;
    const float kWindowAnimation_Rotate_ScaleFactor = .99f;

    const float kWindowAnimation_Bounce_Scale = 1.02f;
    const int kWindowAnimation_Bounce_DurationMS = 180;
    const int kWindowAnimation_Bounce_GrowShrinkDurationPercent = 40;

    base::TimeDelta GetWindowVisibilityAnimationDuration(
        const aura::Window& window)
    {
        int duration = window.GetProperty(kWindowVisibilityAnimationDurationKey);
        if (duration == 0 && window.type() == ui::wm::WINDOW_TYPE_MENU) {
            return base::TimeDelta::FromMilliseconds(
                kDefaultAnimationDurationForMenuMS);
        }
        return base::TimeDelta::FromInternalValue(duration);
    }

    // Gets/sets the WindowVisibilityAnimationType associated with a window.
    // TODO(beng): redundant/fold into method on public api?
    int GetWindowVisibilityAnimationType(aura::Window* window)
    {
        int type = window->GetProperty(kWindowVisibilityAnimationTypeKey);
        if (type == WINDOW_VISIBILITY_ANIMATION_TYPE_DEFAULT) {
            return (window->type() == ui::wm::WINDOW_TYPE_MENU || window->type() == ui::wm::WINDOW_TYPE_TOOLTIP)
                ? WINDOW_VISIBILITY_ANIMATION_TYPE_FADE
                : WINDOW_VISIBILITY_ANIMATION_TYPE_DROP;
        }
        return type;
    }

    void GetTransformRelativeToRoot(ui::Layer* layer, gfx::Transform* transform)
    {
        const ui::Layer* root = layer;
        while (root->parent())
            root = root->parent();
        layer->GetTargetTransformRelativeTo(root, transform);
    }

    gfx::Rect GetLayerWorldBoundsAfterTransform(ui::Layer* layer,
        const gfx::Transform& transform)
    {
        gfx::Transform in_world = transform;
        GetTransformRelativeToRoot(layer, &in_world);

        gfx::RectF transformed = gfx::RectF(layer->bounds());
        in_world.TransformRect(&transformed);

        return gfx::ToEnclosingRect(transformed);
    }

    // Augment the host window so that the enclosing bounds of the full
    // animation will fit inside of it.
    void AugmentWindowSize(aura::Window* window,
        const gfx::Transform& end_transform)
    {
        aura::client::AnimationHost* animation_host = aura::client::GetAnimationHost(window);
        if (!animation_host)
            return;

        const gfx::Rect& world_at_start = window->bounds();
        gfx::Rect world_at_end = GetLayerWorldBoundsAfterTransform(window->layer(), end_transform);
        gfx::Rect union_in_window_space = gfx::UnionRects(world_at_start, world_at_end);

        // Calculate the top left and bottom right deltas to be added to the window
        // bounds.
        gfx::Vector2d top_left_delta(world_at_start.x() - union_in_window_space.x(),
            world_at_start.y() - union_in_window_space.y());

        gfx::Vector2d bottom_right_delta(
            union_in_window_space.x() + union_in_window_space.width() - (world_at_start.x() + world_at_start.width()),
            union_in_window_space.y() + union_in_window_space.height() - (world_at_start.y() + world_at_start.height()));

        DCHECK(top_left_delta.x() >= 0 && top_left_delta.y() >= 0 && bottom_right_delta.x() >= 0 && bottom_right_delta.y() >= 0);

        animation_host->SetHostTransitionOffsets(top_left_delta, bottom_right_delta);
    }

    // Shows a window using an animation, animating its opacity from 0.f to 1.f,
    // its visibility to true, and its transform from |start_transform| to
    // |end_transform|.
    void AnimateShowWindowCommon(aura::Window* window,
        const gfx::Transform& start_transform,
        const gfx::Transform& end_transform)
    {
        AugmentWindowSize(window, end_transform);

        window->layer()->SetOpacity(kWindowAnimation_HideOpacity);
        window->layer()->SetTransform(start_transform);
        window->layer()->SetVisible(true);

        {
            // Property sets within this scope will be implicitly animated.
            ui::ScopedLayerAnimationSettings settings(window->layer()->GetAnimator());
            base::TimeDelta duration = GetWindowVisibilityAnimationDuration(*window);
            if (duration.ToInternalValue() > 0)
                settings.SetTransitionDuration(duration);

            window->layer()->SetTransform(end_transform);
            window->layer()->SetOpacity(kWindowAnimation_ShowOpacity);
        }
    }

    // Hides a window using an animation, animating its opacity from 1.f to 0.f,
    // its visibility to false, and its transform to |end_transform|.
    void AnimateHideWindowCommon(aura::Window* window,
        const gfx::Transform& end_transform)
    {
        AugmentWindowSize(window, end_transform);

        // Property sets within this scope will be implicitly animated.
        ScopedHidingAnimationSettings hiding_settings(window);
        base::TimeDelta duration = GetWindowVisibilityAnimationDuration(*window);
        if (duration.ToInternalValue() > 0)
            hiding_settings.layer_animation_settings()->SetTransitionDuration(duration);

        window->layer()->SetOpacity(kWindowAnimation_HideOpacity);
        window->layer()->SetTransform(end_transform);
        window->layer()->SetVisible(false);
    }

    static gfx::Transform GetScaleForWindow(aura::Window* window)
    {
        gfx::Rect bounds = window->bounds();
        gfx::Transform scale = gfx::GetScaleTransform(
            gfx::Point(kWindowAnimation_TranslateFactor * bounds.width(),
                kWindowAnimation_TranslateFactor * bounds.height()),
            kWindowAnimation_ScaleFactor);
        return scale;
    }

    // Show/Hide windows using a shrink animation.
    void AnimateShowWindow_Drop(aura::Window* window)
    {
        AnimateShowWindowCommon(window, GetScaleForWindow(window), gfx::Transform());
    }

    void AnimateHideWindow_Drop(aura::Window* window)
    {
        AnimateHideWindowCommon(window, GetScaleForWindow(window));
    }

    // Show/Hide windows using a vertical Glenimation.
    void AnimateShowWindow_Vertical(aura::Window* window)
    {
        gfx::Transform transform;
        transform.Translate(0, window->GetProperty(kWindowVisibilityAnimationVerticalPositionKey));
        AnimateShowWindowCommon(window, transform, gfx::Transform());
    }

    void AnimateHideWindow_Vertical(aura::Window* window)
    {
        gfx::Transform transform;
        transform.Translate(0, window->GetProperty(kWindowVisibilityAnimationVerticalPositionKey));
        AnimateHideWindowCommon(window, transform);
    }

    // Show/Hide windows using a fade.
    void AnimateShowWindow_Fade(aura::Window* window)
    {
        AnimateShowWindowCommon(window, gfx::Transform(), gfx::Transform());
    }

    void AnimateHideWindow_Fade(aura::Window* window)
    {
        AnimateHideWindowCommon(window, gfx::Transform());
    }

    ui::LayerAnimationElement* CreateGrowShrinkElement(
        aura::Window* window, bool grow)
    {
        scoped_ptr<ui::InterpolatedTransform> scale(new ui::InterpolatedScale(
            gfx::Point3F(kWindowAnimation_Bounce_Scale,
                kWindowAnimation_Bounce_Scale,
                1),
            gfx::Point3F(1, 1, 1)));
        scoped_ptr<ui::InterpolatedTransform> scale_about_pivot(
            new ui::InterpolatedTransformAboutPivot(
                gfx::Point(window->bounds().width() * 0.5,
                    window->bounds().height() * 0.5),
                scale.release()));
        scale_about_pivot->SetReversed(grow);
        scoped_ptr<ui::LayerAnimationElement> transition(
            ui::LayerAnimationElement::CreateInterpolatedTransformElement(
                scale_about_pivot.release(),
                base::TimeDelta::FromMilliseconds(
                    kWindowAnimation_Bounce_DurationMS * kWindowAnimation_Bounce_GrowShrinkDurationPercent / 100)));
        transition->set_tween_type(grow ? gfx::Tween::EASE_OUT : gfx::Tween::EASE_IN);
        return transition.release();
    }

    void AnimateBounce(aura::Window* window)
    {
        ui::ScopedLayerAnimationSettings scoped_settings(
            window->layer()->GetAnimator());
        scoped_settings.SetPreemptionStrategy(
            ui::LayerAnimator::REPLACE_QUEUED_ANIMATIONS);
        scoped_ptr<ui::LayerAnimationSequence> sequence(
            new ui::LayerAnimationSequence);
        sequence->AddElement(CreateGrowShrinkElement(window, true));
        sequence->AddElement(ui::LayerAnimationElement::CreatePauseElement(
            ui::LayerAnimationElement::BOUNDS,
            base::TimeDelta::FromMilliseconds(
                kWindowAnimation_Bounce_DurationMS * (100 - 2 * kWindowAnimation_Bounce_GrowShrinkDurationPercent) / 100)));
        sequence->AddElement(CreateGrowShrinkElement(window, false));
        window->layer()->GetAnimator()->StartAnimation(sequence.release());
    }

    // A HidingWindowAnimationObserver that deletes observer and detached
    // layers when the last_sequence has been completed or aborted.
    class RotateHidingWindowAnimationObserver
        : public HidingWindowAnimationObserverBase,
          public ui::LayerAnimationObserver {
    public:
        explicit RotateHidingWindowAnimationObserver(aura::Window* window)
            : HidingWindowAnimationObserverBase(window)
        {
        }
        ~RotateHidingWindowAnimationObserver() override { }

        // Destroys itself after |last_sequence| ends or is aborted. Does not take
        // ownership of |last_sequence|, which should not be NULL.
        void SetLastSequence(ui::LayerAnimationSequence* last_sequence)
        {
            last_sequence->AddObserver(this);
        }

        // ui::LayerAnimationObserver:
        void OnLayerAnimationEnded(ui::LayerAnimationSequence* sequence) override
        {
            OnAnimationCompleted();
        }
        void OnLayerAnimationAborted(ui::LayerAnimationSequence* sequence) override
        {
            OnAnimationCompleted();
        }
        void OnLayerAnimationScheduled(
            ui::LayerAnimationSequence* sequence) override { }

    private:
        DISALLOW_COPY_AND_ASSIGN(RotateHidingWindowAnimationObserver);
    };

    void AddLayerAnimationsForRotate(aura::Window* window, bool show)
    {
        if (show)
            window->layer()->SetOpacity(kWindowAnimation_HideOpacity);

        base::TimeDelta duration = base::TimeDelta::FromMilliseconds(
            kWindowAnimation_Rotate_DurationMS);

        RotateHidingWindowAnimationObserver* observer = NULL;

        if (!show) {
            observer = new RotateHidingWindowAnimationObserver(window);
            window->layer()->GetAnimator()->SchedulePauseForProperties(
                duration * (100 - kWindowAnimation_Rotate_OpacityDurationPercent) / 100,
                ui::LayerAnimationElement::OPACITY);
        }
        scoped_ptr<ui::LayerAnimationElement> opacity(
            ui::LayerAnimationElement::CreateOpacityElement(
                show ? kWindowAnimation_ShowOpacity : kWindowAnimation_HideOpacity,
                duration * kWindowAnimation_Rotate_OpacityDurationPercent / 100));
        opacity->set_tween_type(gfx::Tween::EASE_IN_OUT);
        window->layer()->GetAnimator()->ScheduleAnimation(
            new ui::LayerAnimationSequence(opacity.release()));

        float xcenter = window->bounds().width() * 0.5;

        gfx::Transform transform;
        transform.Translate(xcenter, 0);
        transform.ApplyPerspectiveDepth(kWindowAnimation_Rotate_PerspectiveDepth);
        transform.Translate(-xcenter, 0);
        scoped_ptr<ui::InterpolatedTransform> perspective(
            new ui::InterpolatedConstantTransform(transform));

        scoped_ptr<ui::InterpolatedTransform> scale(
            new ui::InterpolatedScale(1, kWindowAnimation_Rotate_ScaleFactor));
        scoped_ptr<ui::InterpolatedTransform> scale_about_pivot(
            new ui::InterpolatedTransformAboutPivot(
                gfx::Point(xcenter, kWindowAnimation_Rotate_TranslateY),
                scale.release()));

        scoped_ptr<ui::InterpolatedTransform> translation(
            new ui::InterpolatedTranslation(
                gfx::PointF(), gfx::PointF(0, kWindowAnimation_Rotate_TranslateY)));

        scoped_ptr<ui::InterpolatedTransform> rotation(
            new ui::InterpolatedAxisAngleRotation(
                gfx::Vector3dF(1, 0, 0), 0, kWindowAnimation_Rotate_DegreesX));

        scale_about_pivot->SetChild(perspective.release());
        translation->SetChild(scale_about_pivot.release());
        rotation->SetChild(translation.release());
        rotation->SetReversed(show);

        scoped_ptr<ui::LayerAnimationElement> transition(
            ui::LayerAnimationElement::CreateInterpolatedTransformElement(
                rotation.release(), duration));
        ui::LayerAnimationSequence* last_sequence = new ui::LayerAnimationSequence(transition.release());
        window->layer()->GetAnimator()->ScheduleAnimation(last_sequence);

        if (observer) {
            observer->SetLastSequence(last_sequence);
            observer->DetachAndRecreateLayers();
        }
    }

    void AnimateShowWindow_Rotate(aura::Window* window)
    {
        AddLayerAnimationsForRotate(window, true);
    }

    void AnimateHideWindow_Rotate(aura::Window* window)
    {
        AddLayerAnimationsForRotate(window, false);
    }

    bool AnimateShowWindow(aura::Window* window)
    {
        if (!HasWindowVisibilityAnimationTransition(window, ANIMATE_SHOW)) {
            if (HasWindowVisibilityAnimationTransition(window, ANIMATE_HIDE)) {
                // Since hide animation may have changed opacity and transform,
                // reset them to show the window.
                window->layer()->SetOpacity(kWindowAnimation_ShowOpacity);
                window->layer()->SetTransform(gfx::Transform());
            }
            return false;
        }

        switch (GetWindowVisibilityAnimationType(window)) {
        case WINDOW_VISIBILITY_ANIMATION_TYPE_DROP:
            AnimateShowWindow_Drop(window);
            return true;
        case WINDOW_VISIBILITY_ANIMATION_TYPE_VERTICAL:
            AnimateShowWindow_Vertical(window);
            return true;
        case WINDOW_VISIBILITY_ANIMATION_TYPE_FADE:
            AnimateShowWindow_Fade(window);
            return true;
        case WINDOW_VISIBILITY_ANIMATION_TYPE_ROTATE:
            AnimateShowWindow_Rotate(window);
            return true;
        default:
            return false;
        }
    }

    bool AnimateHideWindow(aura::Window* window)
    {
        if (!HasWindowVisibilityAnimationTransition(window, ANIMATE_HIDE)) {
            if (HasWindowVisibilityAnimationTransition(window, ANIMATE_SHOW)) {
                // Since show animation may have changed opacity and transform,
                // reset them, though the change should be hidden.
                window->layer()->SetOpacity(kWindowAnimation_HideOpacity);
                window->layer()->SetTransform(gfx::Transform());
            }
            return false;
        }

        switch (GetWindowVisibilityAnimationType(window)) {
        case WINDOW_VISIBILITY_ANIMATION_TYPE_DROP:
            AnimateHideWindow_Drop(window);
            return true;
        case WINDOW_VISIBILITY_ANIMATION_TYPE_VERTICAL:
            AnimateHideWindow_Vertical(window);
            return true;
        case WINDOW_VISIBILITY_ANIMATION_TYPE_FADE:
            AnimateHideWindow_Fade(window);
            return true;
        case WINDOW_VISIBILITY_ANIMATION_TYPE_ROTATE:
            AnimateHideWindow_Rotate(window);
            return true;
        default:
            return false;
        }
    }

} // namespace

////////////////////////////////////////////////////////////////////////////////
// ImplicitHidingWindowAnimationObserver

ImplicitHidingWindowAnimationObserver::ImplicitHidingWindowAnimationObserver(
    aura::Window* window,
    ui::ScopedLayerAnimationSettings* settings)
    : HidingWindowAnimationObserverBase(window)
{
    settings->AddObserver(this);
}

void ImplicitHidingWindowAnimationObserver::OnImplicitAnimationsCompleted()
{
    OnAnimationCompleted();
}

////////////////////////////////////////////////////////////////////////////////
// ScopedHidingAnimationSettings

ScopedHidingAnimationSettings::ScopedHidingAnimationSettings(
    aura::Window* window)
    : layer_animation_settings_(window->layer()->GetAnimator())
    , observer_(new ImplicitHidingWindowAnimationObserver(
          window,
          &layer_animation_settings_))
{
}

ScopedHidingAnimationSettings::~ScopedHidingAnimationSettings()
{
    observer_->DetachAndRecreateLayers();
}

////////////////////////////////////////////////////////////////////////////////
// External interface

void SetWindowVisibilityAnimationType(aura::Window* window, int type)
{
    window->SetProperty(kWindowVisibilityAnimationTypeKey, type);
}

int GetWindowVisibilityAnimationType(aura::Window* window)
{
    return window->GetProperty(kWindowVisibilityAnimationTypeKey);
}

void SetWindowVisibilityAnimationTransition(
    aura::Window* window,
    WindowVisibilityAnimationTransition transition)
{
    window->SetProperty(kWindowVisibilityAnimationTransitionKey, transition);
}

bool HasWindowVisibilityAnimationTransition(
    aura::Window* window,
    WindowVisibilityAnimationTransition transition)
{
    WindowVisibilityAnimationTransition prop = window->GetProperty(
        kWindowVisibilityAnimationTransitionKey);
    return (prop & transition) != 0;
}

void SetWindowVisibilityAnimationDuration(aura::Window* window,
    const base::TimeDelta& duration)
{
    window->SetProperty(kWindowVisibilityAnimationDurationKey,
        static_cast<int>(duration.ToInternalValue()));
}

base::TimeDelta GetWindowVisibilityAnimationDuration(
    const aura::Window& window)
{
    return base::TimeDelta::FromInternalValue(
        window.GetProperty(kWindowVisibilityAnimationDurationKey));
}

void SetWindowVisibilityAnimationVerticalPosition(aura::Window* window,
    float position)
{
    window->SetProperty(kWindowVisibilityAnimationVerticalPositionKey, position);
}

bool AnimateOnChildWindowVisibilityChanged(aura::Window* window, bool visible)
{
    if (WindowAnimationsDisabled(window))
        return false;
    if (visible)
        return AnimateShowWindow(window);
    // Don't start hiding the window again if it's already being hidden.
    return window->layer()->GetTargetOpacity() != 0.0f && AnimateHideWindow(window);
}

bool AnimateWindow(aura::Window* window, WindowAnimationType type)
{
    switch (type) {
    case WINDOW_ANIMATION_TYPE_BOUNCE:
        AnimateBounce(window);
        return true;
    default:
        NOTREACHED();
        return false;
    }
}

bool WindowAnimationsDisabled(aura::Window* window)
{
    // Individual windows can choose to skip animations.
    if (window && window->GetProperty(aura::client::kAnimationsDisabledKey))
        return true;

    // Animations can be disabled globally for testing.
    if (base::CommandLine::ForCurrentProcess()->HasSwitch(
            switches::kWindowAnimationsDisabled))
        return true;

    // Tests of animations themselves should still run even if the machine is
    // being accessed via Remote Desktop.
    if (ui::ScopedAnimationDurationScaleMode::duration_scale_mode() == ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION)
        return false;

    // Let the user decide whether or not to play the animation.
    return !gfx::Animation::ShouldRenderRichAnimation();
}

} // namespace wm
